/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.ext.ocez;

import cn.easyplatform.web.ext.Queryable;
import cn.easyplatform.web.ext.Reloadable;
import cn.easyplatform.web.ext.Widget;
import cn.easyplatform.web.ext.ZkExt;
import cn.easyplatform.web.ext.ocez.event.NodeDropEvent;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.zkoss.lang.Objects;
import org.zkoss.lang.Strings;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;

import java.util.Iterator;
import java.util.List;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class OrgChart extends HtmlBasedComponent implements Queryable, ZkExt, Reloadable {

    /**
     *
     */
    private static final long serialVersionUID = -8489741499316917861L;

    static {
        addClientEvent(OrgChart.class, Events.ON_SELECT, 0);
        addClientEvent(OrgChart.class, Events.ON_OPEN, CE_IMPORTANT
                | CE_REPEAT_IGNORE);
    }

    /**
     * 方向( t2b(implies "top to bottom"|b2t(implies "bottom to top")|l2r(implies "left to right")|r2l(implies "right to left"))
     * ，默认是"t2b"
     */
    private String _orient;
    /**
     * 是否可以拖拉图表
     */
    private boolean _pan;
    /**
     * 是否可以放大缩小
     */
    private boolean _zoom;
    /**
     * ，默认是"false"
     */
    private boolean _toggleSiblingsResp;
    /**
     * 使用font awesome icon提示有子节点，默认是"fa-users"
     */
    private String _parentNodeSymbol;
    /**
     * 0:默认；1：创建带图标的节点
     */
    private boolean _hasImage;
    /**
     * 是否显示导出按钮
     */
    private boolean _exportButton;
    /**
     * 导出文件名
     */
    private String _exportFilename;
    /**
     * 样式
     */
    private String _chartClass;
    /**
     * 是否可以拖动节点
     */
    private String _draggable;

    /**
     * 数据连接的资源id
     */
    private String _dbId;

    /**
     * 查询语句
     */
    private Object _query;

    /**
     * 过滤表达式,target一定存在
     */
    private String _filter;

    /**
     * 在显示时是否要马上执行查询
     */
    private boolean _immediate = true;

    /**
     * 初始打开的层次，默认是"999"
     */
    private int _depth;
    /**
     * 单击选中的节点
     */
    private OrgNode _selectedItem;
    /**
     * 根节点
     */
    private OrgNode _rootNode;
    /**
     * 拖拉事件
     */
    private String dndEvent;
    /**
     * 初始显示的节点id，在延时加载或多层显示时使用
     */
    private String _nodeId;
    /**
     * 是否分层显示不同的颜色
     */
    private boolean _levelColor;
    /**
     * 放大倍数限制,默认值7
     */
    private double _zoominLimit;
    /**
     * 缩小倍数限制,默认值0.5
     */
    private double _zoomoutLimit;

    private int _verticalDepth;
    /**
     * function类型，拖拉时限制拖放的区域控制
     */
    private String _dropCriteria;
    /**
     * function类型,实例生成后
     */
    private String _initCompleted;
    /**
     * 是否延时加载
     */
    private boolean _lazy;
    /**
     * 是否已展开全部
     */
    private boolean _expandAll;

    /**
     * 是否必需重新加载
     */
    private boolean _force;

    public String getFilter() {
        return _filter;
    }

    public void setFilter(String filter) {
        this._filter = filter;
    }

    @Override
    public boolean isForce() {
        return _force;
    }

    public void setForce(boolean force) {
        this._force = force;
    }

    public double getZoominLimit() {
        return _zoominLimit;
    }

    public void setZoominLimit(double zoominLimit) {
        if (zoominLimit != _zoominLimit) {
            this._zoominLimit = zoominLimit;
            smartUpdate("zoominLimit", _zoominLimit);
        }
    }

    public double getZoomoutLimit() {
        return _zoomoutLimit;
    }

    public void setZoomoutLimit(double zoomoutLimit) {
        if (zoomoutLimit != _zoomoutLimit) {
            this._zoomoutLimit = zoomoutLimit;
            smartUpdate("zoomoutLimit", _zoomoutLimit);
        }
    }

    public int getVerticalDepth() {
        return _verticalDepth;
    }

    public void setVerticalDepth(int verticalDepth) {
        if (verticalDepth != _verticalDepth) {
            this._verticalDepth = verticalDepth;
            smartUpdate("verticalDepth", _verticalDepth);
        }
    }

    public String getDropCriteria() {
        return _dropCriteria;
    }

    public void setDropCriteria(String dropCriteria) {
        if (!Objects.equals(_dropCriteria, dropCriteria)) {
            this._dropCriteria = dropCriteria;
            smartUpdate("dropCriteria", _dropCriteria);
        }
    }

    public String getInitCompleted() {
        return _initCompleted;
    }

    public void setInitCompleted(String initCompleted) {
        if (!Objects.equals(_initCompleted, initCompleted)) {
            this._initCompleted = initCompleted;
            smartUpdate("initCompleted", _initCompleted);
        }
    }

    public boolean isLevelColor() {
        return _levelColor;
    }

    public void setLevelColor(boolean levelColor) {
        this._levelColor = levelColor;
    }

    public String getNodeId() {
        return _nodeId;
    }

    public void setNodeId(String nodeId) {
        this._nodeId = nodeId;
    }

    public boolean isLazy() {
        return _lazy;
    }

    public void setLazy(boolean lazy) {
        this._lazy = lazy;
    }

    public String getDndEvent() {
        return dndEvent;
    }

    public void setDndEvent(String dndEvent) {
        this.dndEvent = dndEvent;
    }

    public boolean isPan() {
        return _pan;
    }

    public void setPan(boolean pan) {
        if (this._pan != pan) {
            this._pan = pan;
            smartUpdate("pan", pan);
        }
    }

    public boolean isZoom() {
        return _zoom;
    }

    public void setZoom(boolean zoom) {
        if (this._zoom != zoom) {
            this._zoom = zoom;
            smartUpdate("zoom", zoom);
        }
    }

    public boolean isToggleSiblingsResp() {
        return _toggleSiblingsResp;
    }

    public void setToggleSiblingsResp(boolean toggleSiblingsResp) {
        if (this._toggleSiblingsResp != toggleSiblingsResp) {
            this._toggleSiblingsResp = toggleSiblingsResp;
            smartUpdate("toggleSiblingsResp", toggleSiblingsResp);
        }
    }

    public String getParentNodeSymbol() {
        return _parentNodeSymbol;
    }

    public void setParentNodeSymbol(String parentNodeSymbol) {
        if (!Objects.equals(parentNodeSymbol, _parentNodeSymbol)) {
            this._parentNodeSymbol = parentNodeSymbol;
            smartUpdate("parentNodeSymbol", parentNodeSymbol);
        }
    }

    public void setHasImage(boolean hasImage) {
        if (hasImage != _hasImage) {
            this._hasImage = hasImage;
            smartUpdate("createNode", hasImage);
        }
    }

    public boolean isExportButton() {
        return _exportButton;
    }

    public void setExportButton(boolean exportButton) {
        if (this._exportButton != exportButton) {
            this._exportButton = exportButton;
            smartUpdate("exportButton", exportButton);
        }
    }

    public String getExportFilename() {
        return _exportFilename;
    }

    public void setExportFilename(String exportFilename) {
        if (!Objects.equals(exportFilename, _exportFilename)) {
            this._exportFilename = exportFilename;
            smartUpdate("exportFilename", exportFilename);
        }
    }

    public String getChartClass() {
        return _chartClass;
    }

    public void setChartClass(String chartClass) {
        if (!Objects.equals(_chartClass, chartClass)) {
            this._chartClass = chartClass;
            smartUpdate("chartClass", chartClass);
        }
    }

    public int getDepth() {
        return _depth;
    }

    public void setDepth(int depth) {
        if (this._depth != depth) {
            this._depth = depth;
            smartUpdate("depth", depth);
        }
    }

    public Object getQuery() {
        return _query;
    }

    public void setQuery(Object query) {
        this._query = query;
    }

    public String getDbId() {
        return _dbId;
    }

    public void setDbId(String dbId) {
        this._dbId = dbId;
    }

    public String getOrient() {
        return _orient;
    }

    public void setOrient(String orient) {
        if (orient != null && orient.length() == 0)
            orient = null;
        if (!Objects.equals(orient, _orient)) {
            this._orient = orient;
            smartUpdate("direction", orient);
        }
    }

    public boolean isImmediate() {
        return _immediate;
    }

    public void setImmediate(boolean immediate) {
        this._immediate = immediate;
    }

    public boolean isChildable() {
        return false;
    }

    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
            throws java.io.IOException {
        super.renderProperties(renderer);
        if (!Strings.isBlank(_orient))
            render(renderer, "direction", _orient);
        if (_depth > 0)
            render(renderer, "depth", _depth);
        if (!Strings.isBlank(_parentNodeSymbol))
            render(renderer, "parentNodeSymbol", _parentNodeSymbol);
        if (!Strings.isBlank(_exportFilename))
            render(renderer, "exportFilename", _exportFilename);
        if (!Strings.isBlank(_chartClass))
            render(renderer, "chartClass", _chartClass);
        if (!Strings.isBlank(_draggable))
            render(renderer, "dragg", _draggable.equalsIgnoreCase("true"));
        if (_rootNode != null) {
            String data = null;
            if (!_lazy)
                data = toJson(null, null);
            else
                data = toJson(this._nodeId == null ? _rootNode.getId() : this._nodeId, "down");
            if (data != null)
                render(renderer, "data", data);
            //System.out.println(data);
        }
        if (_verticalDepth > 0)
            render(renderer, "verticalDepth", _verticalDepth);
        if (_zoominLimit > 0)
            render(renderer, "zoominLimit", _zoominLimit);
        if (_zoomoutLimit > 0)
            render(renderer, "zoomoutLimit", _zoomoutLimit);
        if (Strings.isBlank(_dropCriteria))
            render(renderer, "dropCriteria", _dropCriteria);
        if (Strings.isBlank(_initCompleted))
            render(renderer, "initCompleted", _initCompleted);
        render(renderer, "pan", _pan);
        render(renderer, "zoom", _zoom);
        render(renderer, "toggleSiblingsResp", _toggleSiblingsResp);
        render(renderer, "exportButton", _exportButton);
        render(renderer, "createNode", _hasImage);
    }

    private String toJson(String nodeId, String type) {
        Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
        JsonParser jp = new JsonParser();
        if (nodeId == null) {
            JsonElement je = jp.parse(gson.toJson(_rootNode));
            return gson.toJson(je);
        }
        OrgNode node = travelNode(nodeId, _rootNode.getChildren());
        if (node != null) {
            if ("up".equals(type)) {//往上回朔父节点
                JsonElement je = jp.parse(gson.toJson(node.getParent().clone(false)));
                return gson.toJson(je);
            } else if ("down".equals(type) || "layer".equals(type)) {//往下子节点
                JsonElement je = jp.parse(gson.toJson(node.clone(true)));
                return gson.toJson(je);
            } else if ("sibling".equals(type)) {//同层节点
                OrgNode parent = node.getParent().clone(true);
                Iterator<OrgNode> itr = parent.getChildren().iterator();
                while (itr.hasNext()) {
                    OrgNode n = itr.next();
                    if (n.getId().equals(nodeId)) {
                        itr.remove();
                        break;
                    }
                }
                JsonElement je = jp.parse(gson.toJson(parent));
                return gson.toJson(je);
            }
        }
        return null;
    }

    @Override
    public void setDraggable(String draggable) {
        if (!Objects.equals(draggable, _draggable)) {
            this._draggable = draggable;
            smartUpdate("dragg", "true".equalsIgnoreCase(draggable));
        }
    }

    @Override
    public String getDraggable() {
        return this._draggable;
    }

    public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
        String cmd = request.getCommand();
        if (cmd.equals(Events.ON_SELECT) || cmd.equals(Events.ON_DOUBLE_CLICK)) {
            Object id = request.getData().get("id");
            if (_rootNode.getId().equals(id))
                this._selectedItem = _rootNode;
            else if (this.getChildren() != null)
                this._selectedItem = travelNode(id, _rootNode.getChildren());
            if (this._selectedItem != null)
                Events.postEvent(this, new Event(cmd, this, id));
        } else if (cmd.equals(Events.ON_DROP)) {
            Events.postEvent(NodeDropEvent.getNodeEvent(request));
        } else if (cmd.equals(Events.ON_OPEN)) {
            String id = (String) request.getData().get("id");
            String type = (String) request.getData().get("tp");
            String json = toJson(id, type);
            if (json != null) {
                if (type.equals("layer"))
                    smartUpdate("data", json);
                else
                    smartUpdate("fragment", json);
            }
        } else
            super.service(request, everError);
    }

    private OrgNode travelNode(Object id, List<OrgNode> children) {
        if (id.equals(_rootNode.getId()))
            return _rootNode;
        for (OrgNode n : children) {
            if (n.getId().equals(id))
                return n;
            if (n.getChildren() != null) {
                OrgNode c = travelNode(id, n.getChildren());
                if (c != null)
                    return c;
            }
        }
        return null;
    }

    public void expandAll() {
        Object value = false;
        if (!_expandAll && _lazy) {
            value = toJson(null, null);
            _expandAll = true;
        }
        smartUpdate("expandAll", value);
    }

    public void collapseAll() {
        smartUpdate("collapseAll", true);
    }

    @Override
    public void reload() {
        Widget ext = (Widget) getAttribute("$proxy");
        ext.reload(this);
    }

    public OrgNode getSelectedItem() {
        return _selectedItem;
    }

    public void setSelectedItem(OrgNode selectedItem) {
        this._selectedItem = selectedItem;
    }

    public OrgNode getRootNode() {
        return _rootNode;
    }

    public void setRootNode(OrgNode rootNode) {
        this._rootNode = rootNode;
    }
}
